/*
日志落地模块设计
1.抽象出不同落地子类的基类
2.派生出不同的落地子类
3.使用工厂模式进行创建与表示的分离
*/

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "util.hpp"
#include <fstream>
#include <sstream>
#include <cassert>
#include <memory>

namespace clog
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink()
        {
        }
        virtual ~LogSink()
        {
        }
        virtual void log(const char *data, size_t len) = 0;
    };

    // 落地方向：标准输出
    class StdoutSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<StdoutSink>;
        StdoutSink() = default;
        void log(const char *data, size_t len) override
        {
            std::cout.write(data, len);
        }
    };

    // 落地方向：普通文件
    class FileSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<FileSink>;
        // 传入文件名，打开文件，将操作句柄管理起来
        FileSink(const std::string &pathname)
            : _pathname(pathname)
        {
            // 1.创建文件所在的目录
            util::File::creatDirectory(util::File::path(_pathname));
            // 2.打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app);
            // 判断文件是否打开成功
            assert(_ofs.is_open());
        }

        void log(const char *data, size_t len) override
        {
            _ofs.write(data, len);
            if (_ofs.good() == false)
            {
                std::cout << "日志文件输出失败！\n";
            }
        }

    private:
        std::string _pathname;
        std::ofstream _ofs; // 为了避免每次对文件写入都要打开文件，对操作句柄进行管理
    };

    // 落地方向：滚动文件
    class RollBySizeSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<FileSink>;
        RollBySizeSink(const std::string &basename, size_t max_fsize)
            : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0), _name_count(0)
        {
            std::string filename = creatNewFile();
            // 1.创建文件所在的目录
            util::File::creatDirectory(util::File::path(filename));
            // 2.打开文件
            _ofs.open(filename, std::ios::binary | std::ios::app);
            // 判断文件是否打开成功
            assert(_ofs.is_open());
        }

        void log(const char *data, size_t len) override
        {
            if (_cur_fsize >= _max_fsize)
            {
                _ofs.close(); // 关闭旧文件，避免内存泄露
                std::string filename = creatNewFile();
                _ofs.open(filename);
                assert(_ofs.is_open());
                _cur_fsize = 0;
            }
            _ofs.write(data, len);
            if (_ofs.good() == false)
            {
                std::cout << "日志文件输出失败！\n";
            }
            _cur_fsize += len;
        }

    private:
        // 进行文件大小判断，超过指定大小创建新文件
        std::string creatNewFile()
        {
            // 获取系统时间
            time_t t = util::Date::now();
            struct tm tl;
            localtime_r(&t, &tl);
            std::stringstream ss;
            ss << _basename;
            ss << tl.tm_year + 1900;
            ss << tl.tm_mon + 1;
            ss << tl.tm_mday;
            ss << tl.tm_hour;
            ss << tl.tm_min;
            ss << tl.tm_sec;
            ss << "-";
            ss << _name_count++;
            ss << ".log";
            return ss.str();
        }

    private:
        // 文件名=基础文件名+以文件大小进行切换的不同扩展文件名
        std::string _basename;
        std::ofstream _ofs;
        size_t _max_fsize;
        size_t _cur_fsize;
        size_t _name_count;
    };

    class SinkFactory
    {
    public:
        template <typename sinktype, typename... Args>
        static LogSink::ptr creat(Args &&...args)
        {
            return std::make_shared<sinktype>(std::forward<Args>(args)...);
        }
    };

}
#endif